﻿using System;
using System.IO;
using System.Text;
using System.Collections;
using System.Threading;
using System.Net.Sockets;
using System.Runtime.Serialization;
namespace SantaSLittleIrcHelper.Irc
{
    public delegate void ReadLineEventHandler(object sender, ReadLineEventArgs e);
    public delegate void WriteLineEventHandler(object sender, WriteLineEventArgs e);
    public delegate void AutoConnectErrorEventHandler(object sender, AutoConnectErrorEventArgs e);
    public delegate void ConnectionStatusChangedEventHandler(object sender, ConnectionStatusChangedEventArgs e);

    # region EventArgs
    public class ConnectionStatusChangedEventArgs : EventArgs
    {
        private StatusFlag _status;
        public StatusFlag Status
        {
            get
            {
                return _status;
            }
        }
        internal ConnectionStatusChangedEventArgs(StatusFlag status)
        {
            _status = status;
        }
    }

    public class ReadLineEventArgs : EventArgs
    {
        private string _Line;
        public string Line
        {
            get
            {
                return _Line;
            }
        }
        internal ReadLineEventArgs(string line)
        {
            _Line = line;
        }
    }
    public class WriteLineEventArgs : EventArgs
    {
        private string _Line;
        public string Line
        {
            get
            {
                return _Line;
            }
        }
        internal WriteLineEventArgs(string line)
        {
            _Line = line;
        }
    }
    public class AutoConnectErrorEventArgs : EventArgs
    {
        private Exception _Exception;
        private string _Address;
        private int _Port;
        public Exception Exception
        {
            get
            {
                return _Exception;
            }
        }
        public string Address
        {
            get
            {
                return _Address;
            }
        }
        public int Port
        {
            get
            {
                return _Port;
            }
        }
        internal AutoConnectErrorEventArgs(string address, int port, Exception ex)
        {
            _Address = address;
            _Port = port;
            _Exception = ex;
        }
    }
    # endregion EventArgs

    # region Exceptions
    /// <threadsafety static="true" instance="true" />
    [Serializable()]
    public class SmartIrc4netException : ApplicationException
    {
        public SmartIrc4netException()
            : base()
        {
        }
        public SmartIrc4netException(string message)
            : base(message)
        {
        }
        public SmartIrc4netException(string message, Exception e)
            : base(message, e)
        {
        }
        protected SmartIrc4netException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }
    /// <threadsafety static="true" instance="true" />
    [Serializable()]
    public class ConnectionException : SmartIrc4netException
    {
        public ConnectionException()
            : base()
        {
        }
        public ConnectionException(string message)
            : base(message)
        {
        }
        public ConnectionException(string message, Exception e)
            : base(message, e)
        {
        }
        protected ConnectionException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }
    /// <threadsafety static="true" instance="true" />
    [Serializable()]
    public class CouldNotConnectException : ConnectionException
    {
        public CouldNotConnectException()
            : base()
        {
        }
        public CouldNotConnectException(string message)
            : base(message)
        {
        }
        public CouldNotConnectException(string message, Exception e)
            : base(message, e)
        {
        }
        protected CouldNotConnectException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }
    /// <threadsafety static="true" instance="true" />
    [Serializable()]
    public class NotConnectedException : ConnectionException
    {
        public NotConnectedException()
            : base()
        {
        }
        public NotConnectedException(string message)
            : base(message)
        {
        }
        public NotConnectedException(string message, Exception e)
            : base(message, e)
        {
        }
        protected NotConnectedException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }
    /// <threadsafety static="true" instance="true" />
    [Serializable()]
    public class AlreadyConnectedException : ConnectionException
    {
        public AlreadyConnectedException()
            : base()
        {
        }
        public AlreadyConnectedException(string message)
            : base(message)
        {
        }
        public AlreadyConnectedException(string message, Exception e)
            : base(message, e)
        {
        }
        protected AlreadyConnectedException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }
    # endregion Exceptions

    # region Constants
    /// <summary>
    ///
    /// </summary>
    public enum Priority
    {
        Low,
        BelowMedium,
        Medium,
        AboveMedium,
        High,
        Critical
    }
    /// <summary>
    ///
    /// </summary>
    public enum SendType
    {
        Message,
        Action,
        Notice,
        CtcpReply,
        CtcpRequest
    }
    /// <summary>
    ///
    /// </summary>
    public enum ReceiveType
    {
        Info,
        Login,
        Motd,
        List,
        Join,
        Kick,
        Part,
        Invite,
        Quit,
        Who,
        WhoIs,
        WhoWas,
        Name,
        Topic,
        BanList,
        NickChange,
        TopicChange,
        UserMode,
        UserModeChange,
        ChannelMode,
        ChannelModeChange,
        ChannelMessage,
        ChannelAction,
        ChannelNotice,
        QueryMessage,
        QueryAction,
        QueryNotice,
        CtcpReply,
        CtcpRequest,
        Error,
        ErrorMessage,
        Unknown
    }
    /// <summary>
    ///
    /// </summary>
    public enum ReplyCode : int
    {
        Null = 000,
        Welcome = 001,
        YourHost = 002,
        Created = 003,
        MyInfo = 004,
        Bounce = 005,
        TraceLink = 200,
        TraceConnecting = 201,
        TraceHandshake = 202,
        TraceUnknown = 203,
        TraceOperator = 204,
        TraceUser = 205,
        TraceServer = 206,
        TraceService = 207,
        TraceNewType = 208,
        TraceClass = 209,
        TraceReconnect = 210,
        StatsLinkInfo = 211,
        StatsCommands = 212,
        EndOfStats = 219,
        UserModeIs = 221,
        ServiceList = 234,
        ServiceListEnd = 235,
        StatsUptime = 242,
        StatsOLine = 243,
        LuserClient = 251,
        LuserOp = 252,
        LuserUnknown = 253,
        LuserChannels = 254,
        LuserMe = 255,
        AdminMe = 256,
        AdminLocation1 = 257,
        AdminLocation2 = 258,
        AdminEmail = 259,
        TraceLog = 261,
        TraceEnd = 262,
        TryAgain = 263,
        Away = 301,
        UserHost = 302,
        IsOn = 303,
        UnAway = 305,
        NowAway = 306,
        WhoIsUser = 311,
        WhoIsServer = 312,
        WhoIsOperator = 313,
        WhoWasUser = 314,
        EndOfWho = 315,
        WhoIsIdle = 317,
        EndOfWhoIs = 318,
        WhoIsChannels = 319,
        ListStart = 321,
        List = 322,
        ListEnd = 323,
        ChannelModeIs = 324,
        UniqueOpIs = 325,
        NoTopic = 331,
        Topic = 332,
        Inviting = 341,
        Summoning = 342,
        InviteList = 346,
        EndOfInviteList = 347,
        ExceptionList = 348,
        EndOfExceptionList = 349,
        Version = 351,
        WhoReply = 352,
        NamesReply = 353,
        Links = 364,
        EndOfLinks = 365,
        EndOfNames = 366,
        BanList = 367,
        EndOfBanList = 368,
        EndOfWhoWas = 369,
        Info = 371,
        Motd = 372,
        EndOfInfo = 374,
        MotdStart = 375,
        EndOfMotd = 376,
        YouAreOper = 381,
        Rehashing = 382,
        YouAreService = 383,
        Time = 391,
        UsersStart = 392,
        Users = 393,
        EndOfUsers = 394,
        NoUsers = 395,
        ErrorNoSuchNickname = 401,
        ErrorNoSuchServer = 402,
        ErrorNoSuchChannel = 403,
        ErrorCannotSendToChannel = 404,
        ErrorTooManyChannels = 405,
        ErrorWasNoSuchNickname = 406,
        ErrorTooManyTargets = 407,
        ErrorNoSuchService = 408,
        ErrorNoOrigin = 409,
        ErrorNoRecipient = 411,
        ErrorNoTextToSend = 412,
        ErrorNoTopLevel = 413,
        ErrorWildTopLevel = 414,
        ErrorBadMask = 415,
        ErrorUnknownCommand = 421,
        ErrorNoMotd = 422,
        ErrorNoAdminInfo = 423,
        ErrorFileError = 424,
        ErrorNoNicknameGiven = 431,
        ErrorErroneusNickname = 432,
        ErrorNicknameInUse = 433,
        ErrorNicknameCollision = 436,
        ErrorUnavailableResource = 437,
        ErrorUserNotInChannel = 441,
        ErrorNotOnChannel = 442,
        ErrorUserOnChannel = 443,
        ErrorNoLogin = 444,
        ErrorSummonDisabled = 445,
        ErrorUsersDisabled = 446,
        ErrorNotRegistered = 451,
        ErrorNeedMoreParams = 461,
        ErrorAlreadyRegistered = 462,
        ErrorNoPermissionForHost = 463,
        ErrorPasswordMismatch = 464,
        ErrorYouAreBannedCreep = 465,
        ErrorYouWillBeBanned = 466,
        ErrorKeySet = 467,
        ErrorChannelIsFull = 471,
        ErrorUnknownMode = 472,
        ErrorInviteOnlyChannel = 473,
        ErrorBannedFromChannel = 474,
        ErrorBadChannelKey = 475,
        ErrorBadChannelMask = 476,
        ErrorNoChannelModes = 477,
        ErrorBanListFull = 478,
        ErrorNoPrivileges = 481,
        ErrorChannelOpPrivilegesNeeded = 482,
        ErrorCannotKillServer = 483,
        ErrorRestricted = 484,
        ErrorUniqueOpPrivilegesNeeded = 485,
        ErrorNoOperHost = 491,
        ErrorUserModeUnknownFlag = 501,
        ErrorUsersDoNotMatch = 502
    }
    # endregion Constants

    class IrcTcpClient : TcpClient
    {
        public Socket Socket
        {
            get
            {
                return Client;
            }
        }
    }

    public class IrcConnection
    {
        private string[] _AddressList = { "localhost" };
        private int _CurrentAddress;
        private int _Port = 6667;
        private StreamReader _Reader;
        private StreamWriter _Writer;
        private ReadThread _ReadThread;
        private WriteThread _WriteThread;
        private IdleWorkerThread _IdleWorkerThread;
        private IrcTcpClient _TcpClient;
        private Hashtable _SendBuffer = Hashtable.Synchronized(new Hashtable());
        private int _SendDelay = 200;
        private bool _IsRegistered;
        private bool _IsConnected;
        private bool _IsConnectionError;
        private int _ConnectTries;
        private bool _AutoRetry;
        private int _AutoRetryDelay = 30;
        private bool _AutoReconnect;
        private Encoding _Encoding = Encoding.Default;
        private int _SocketReceiveTimeout = 600;
        private int _SocketSendTimeout = 600;
        private int _IdleWorkerInterval = 60;
        private int _PingInterval = 60;
        private int _PingTimeout = 300;
        private DateTime _LastPingSent;
        private DateTime _LastPongReceived;
        private TimeSpan _Lag;

        /// <event cref="OnReadLine">
        /// Raised when a \r\n terminated line is read from the socket
        /// </event>
        public event ReadLineEventHandler OnReadLine;

        /// <event cref="OnReadLine">
        /// Raised when the connection status changes
        /// </event>
        public event ConnectionStatusChangedEventHandler OnConnectionStatusChanged;

        /// <event cref="OnWriteLine">
        /// Raised when a \r\n terminated line is written to the socket
        /// </event>
        public event WriteLineEventHandler OnWriteLine;

        /// <event cref="OnConnect">
        /// Raised before the connect attempt
        /// </event>
        public event EventHandler OnConnecting;

        /// <event cref="OnConnect">
        /// Raised on successful connect
        /// </event>
        public event EventHandler OnConnected;

        /// <event cref="OnConnect">
        /// Raised before the connection is closed
        /// </event>
        public event EventHandler OnDisconnecting;

        /// <event cref="OnConnect">
        /// Raised when the connection is closed
        /// </event>
        public event EventHandler OnDisconnected;

        /// <event cref="OnConnectionError">
        /// Raised when the connection got into an error state
        /// </event>
        public event EventHandler OnConnectionError;
        /// <event cref="AutoConnectErrorEventHandler">
        /// Raised when the connection got into an error state during auto connect loop
        /// </event>

        public event AutoConnectErrorEventHandler OnAutoConnectError;
        /// <summary>
        /// When a connection error is detected this property will return true
        /// </summary>

        protected bool IsConnectionError
        {
            get
            {
                lock (this)
                {
                    return _IsConnectionError;
                }
            }
            set
            {
                lock (this)
                {
                    _IsConnectionError = value;
                }
            }
        }

        /// <summary>
        /// Gets the current address of the connection
        /// </summary>
        public string Address
        {
            get
            {
                return _AddressList[_CurrentAddress];
            }
        }

        /// <summary>
        /// Gets the address list of the connection
        /// </summary>
        public string[] AddressList
        {
            get
            {
                return _AddressList;
            }
        }

        /// <summary>
        /// Gets the used port of the connection
        /// </summary>
        public int Port
        {
            get
            {
                return _Port;
            }
        }

        /// <summary>
        /// By default nothing is done when the library looses the connection
        /// to the server.
        /// Default: false
        /// </summary>
        /// <value>
        /// true, if the library should reconnect on lost connections
        /// false, if the library should not take care of it
        /// </value>
        public bool AutoReconnect
        {
            get
            {
                return _AutoReconnect;
            }
            set
            {
                if (value)
                {
                }
                else
                {
                }
                _AutoReconnect = value;
            }
        }

        /// <summary>
        /// If the library should retry to connect when the connection fails.
        /// Default: false
        /// </summary>
        /// <value>
        /// true, if the library should retry to connect
        /// false, if the library should not retry
        /// </value>
        public bool AutoRetry
        {
            get
            {
                return _AutoRetry;
            }
            set
            {
                if (value)
                {
                }
                else
                {
                }
                _AutoRetry = value;
            }
        }

        /// <summary>
        /// Delay between retry attempts in Connect() in seconds.
        /// Default: 30
        /// </summary>
        public int AutoRetryDelay
        {
            get
            {
                return _AutoRetryDelay;
            }
            set
            {
                _AutoRetryDelay = value;
            }
        }

        /// <summary>
        /// To prevent flooding the IRC server, it's required to delay each
        /// message, given in milliseconds.
        /// Default: 200
        /// </summary>
        public int SendDelay
        {
            get
            {
                return _SendDelay;
            }
            set
            {
                _SendDelay = value;
            }
        }

        /// <summary>
        /// On successful registration on the IRC network, this is set to true.
        /// </summary>
        public bool IsRegistered
        {
            get
            {
                return _IsRegistered;
            }
        }

        /// <summary>
        /// On successful connect to the IRC server, this is set to true.
        /// </summary>
        public bool IsConnected
        {
            get
            {
                return _IsConnected;
            }
        }

        /// <summary>
        /// Encoding which is used for reading and writing to the socket
        /// Default: encoding of the system
        /// </summary>
        public Encoding Encoding
        {
            get
            {
                return _Encoding;
            }
            set
            {
                _Encoding = value;
            }
        }

        /// <summary>
        /// Timeout in seconds for receiving data from the socket
        /// Default: 600
        /// </summary>
        public int SocketReceiveTimeout
        {
            get
            {
                return _SocketReceiveTimeout;
            }
            set
            {
                _SocketReceiveTimeout = value;
            }
        }

        /// <summary>
        /// Timeout in seconds for sending data to the socket
        /// Default: 600
        /// </summary>
        public int SocketSendTimeout
        {
            get
            {
                return _SocketSendTimeout;
            }
            set
            {
                _SocketSendTimeout = value;
            }
        }

        /// <summary>
        /// Interval in seconds to run the idle worker
        /// Default: 60
        /// </summary>
        public int IdleWorkerInterval
        {
            get
            {
                return _IdleWorkerInterval;
            }
            set
            {
                _IdleWorkerInterval = value;
            }
        }

        /// <summary>
        /// Interval in seconds to send a PING
        /// Default: 60
        /// </summary>
        public int PingInterval
        {
            get
            {
                return _PingInterval;
            }
            set
            {
                _PingInterval = value;
            }
        }

        /// <summary>
        /// Timeout in seconds for server response to a PING
        /// Default: 600
        /// </summary>
        public int PingTimeout
        {
            get
            {
                return _PingTimeout;
            }
            set
            {
                _PingTimeout = value;
            }
        }

        /// <summary>
        /// Latency between client and the server
        /// </summary>
        public TimeSpan Lag
        {
            get
            {
                return _Lag;
            }
        }

        /// <summary>
        /// Initializes the message queues, read and write thread
        /// </summary>
        public IrcConnection()
        {
            _SendBuffer[Priority.High] = Queue.Synchronized(new Queue());
            _SendBuffer[Priority.AboveMedium] = Queue.Synchronized(new Queue());
            _SendBuffer[Priority.Medium] = Queue.Synchronized(new Queue());
            _SendBuffer[Priority.BelowMedium] = Queue.Synchronized(new Queue());
            _SendBuffer[Priority.Low] = Queue.Synchronized(new Queue());
            // setup own callbacks
            OnReadLine += new ReadLineEventHandler(_SimpleParser);
            OnConnectionError += new EventHandler(_OnConnectionError);
            _ReadThread = new ReadThread(this);
            _WriteThread = new WriteThread(this);
            _IdleWorkerThread = new IdleWorkerThread(this);
        }

        ~IrcConnection()
        {
        }

        /// <overloads>this method has 2 overloads</overloads>
        /// <summary>
        /// Connects to the specified server and port, when the connection fails
        /// the next server in the list will be used.
        /// </summary>
        /// <param name="addresslist">List of servers to connect to</param>
        /// <param name="port">Portnumber to connect to</param>
        /// <exception cref="CouldNotConnectException">The connection failed</exception>
        /// <exception cref="AlreadyConnectedException">If there is already an active connection</exception>
        public void Connect(string[] addresslist, int port)
        {
            if (_IsConnected)
            {
                throw new AlreadyConnectedException("Already connected to: " + Address + ":" + Port);
            }
            _ConnectTries++;
            _AddressList = (string[])addresslist.Clone();
            _Port = port;
            if (OnConnecting != null)
            {
                OnConnecting(this, EventArgs.Empty);
            }
            if (OnConnectionStatusChanged != null)
            {
                OnConnectionStatusChanged(this, new ConnectionStatusChangedEventArgs(StatusFlag.Connecting));
            }
            try
            {
                System.Net.IPAddress ip = System.Net.Dns.GetHostEntry(Address).AddressList[0];
                _TcpClient = new IrcTcpClient();
                _TcpClient.NoDelay = true;
                _TcpClient.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1);
                // set timeout, after this the connection will be aborted
                _TcpClient.ReceiveTimeout = _SocketReceiveTimeout * 1000;
                _TcpClient.SendTimeout = _SocketSendTimeout * 1000;
                _TcpClient.Connect(ip, port);
                _Reader = new StreamReader(_TcpClient.GetStream(), _Encoding);
                _Writer = new StreamWriter(_TcpClient.GetStream(), _Encoding);
                if (_Encoding.GetPreamble().Length > 0)
                {
                    // HACK: we have an encoding that has some kind of preamble
                    // like UTF-8 has a BOM, this will confuse the IRCd!
                    // Thus we send a \r\n so the IRCd can safely ignore that
                    // garbage.
                    _Writer.WriteLine();
                }
                // Connection was successful, reseting the connect counter
                _ConnectTries = 0;
                // updating the connection error state, so connecting is possible again
                IsConnectionError = false;
                _IsConnected = true;
                // lets power up our threads
                _ReadThread.Start();
                _WriteThread.Start();
                _IdleWorkerThread.Start();
                if (OnConnected != null)
                {
                    OnConnected(this, EventArgs.Empty);
                }
                if (OnConnectionStatusChanged != null)
                {
                    OnConnectionStatusChanged(this, new ConnectionStatusChangedEventArgs(StatusFlag.Connected));
                }
            }
            catch (Exception e)
            {
                if (_Reader != null)
                {
                    try
                    {
                        _Reader.Close();
                    }
                    catch (ObjectDisposedException)
                    {
                    }
                }
                if (_Writer != null)
                {
                    try
                    {
                        _Writer.Close();
                    }
                    catch (ObjectDisposedException)
                    {
                    }
                }
                if (_TcpClient != null)
                {
                    _TcpClient.Close();
                }
                _IsConnected = false;
                IsConnectionError = true;
                if (_AutoRetry &&
                    _ConnectTries <= 3)
                {
                    if (OnAutoConnectError != null)
                    {
                        OnAutoConnectError(this, new AutoConnectErrorEventArgs(Address, Port, e));
                    }
                    Thread.Sleep(_AutoRetryDelay * 1000);
                    _NextAddress();
                    Connect(_AddressList, _Port);
                }
                else
                {
                    throw new CouldNotConnectException("Could not connect to: " + Address + ":" + Port + " " + e.Message, e);
                }
            }
        }
        /// <summary>
        /// Connects to the specified server and port.
        /// </summary>
        /// <param name="address">Server address to connect to</param>
        /// <param name="port">Port number to connect to</param>
        public void Connect(string address, int port)
        {
            Connect(new string[] { address }, port);
        }
        /// <summary>
        /// Reconnects to the server
        /// </summary>
        /// <exception cref="NotConnectedException">
        /// If there was no active connection
        /// </exception>
        /// <exception cref="CouldNotConnectException">
        /// The connection failed
        /// </exception>
        /// <exception cref="AlreadyConnectedException">
        /// If there is already an active connection
        /// </exception>
        public void Reconnect()
        {
            Disconnect();
            Connect(_AddressList, _Port);
        }
        /// <summary>
        /// Disconnects from the server
        /// </summary>
        /// <exception cref="NotConnectedException">
        /// If there was no active connection
        /// </exception>
        public void Disconnect()
        {
            if (!IsConnected)
            {
                throw new NotConnectedException("The connection could not be disconnected because there is no active connection");
            }
            if (OnDisconnecting != null)
            {
                OnDisconnecting(this, EventArgs.Empty);
            }
            if (OnConnectionStatusChanged != null)
            {
                OnConnectionStatusChanged(this, new ConnectionStatusChangedEventArgs(StatusFlag.Disconnecting));
            }
            _ReadThread.Stop();
            _WriteThread.Stop();
            _TcpClient.Close();
            _IsConnected = false;
            _IsRegistered = false;
            if (OnDisconnected != null)
            {
                OnDisconnected(this, EventArgs.Empty);
            }
            if (OnConnectionStatusChanged != null)
            {
                OnConnectionStatusChanged(this, new ConnectionStatusChangedEventArgs(StatusFlag.Disconnected));
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="blocking"></param>
        public void Listen(bool blocking)
        {
            if (blocking)
            {
                while (IsConnected)
                {
                    ReadLine(true);
                }
            }
            else
            {
                while (ReadLine(false).Length > 0)
                {
                    // loop as long as we receive messages
                }
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public void Listen()
        {
            Listen(true);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="blocking"></param>
        public void ListenOnce(bool blocking)
        {
            ReadLine(blocking);
        }
        /// <summary>
        /// 
        /// </summary>
        public void ListenOnce()
        {
            ListenOnce(true);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="blocking"></param>
        /// <returns></returns>
        public string ReadLine(bool blocking)
        {
            string data = "";
            if (blocking)
            {
                // block till the queue has data, but bail out on connection error
                while (IsConnected &&
                       !IsConnectionError &&
                       _ReadThread.Queue.Count == 0)
                {
                    Thread.Sleep(10);
                }
            }
            if (IsConnected &&
                _ReadThread.Queue.Count > 0)
            {
                data = (string)(_ReadThread.Queue.Dequeue());
            }
            if (data != null && data.Length > 0)
            {
                if (OnReadLine != null)
                {
                    OnReadLine(this, new ReadLineEventArgs(data));
                }
            }
            if (IsConnectionError &&
                OnConnectionError != null)
            {
                OnConnectionError(this, EventArgs.Empty);
            }
            return data;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="data"></param>
        /// <param name="priority"></param>
        public void WriteLine(string data, Priority priority)
        {
            if (priority == Priority.Critical)
            {
                if (!IsConnected)
                {
                    throw new NotConnectedException();
                }
                _WriteLine(data);
            }
            else
            {
                ((Queue)_SendBuffer[priority]).Enqueue(data);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="data"></param>
        public void WriteLine(string data)
        {
            WriteLine(data, Priority.Medium);
        }
        private bool _WriteLine(string data)
        {
            if (IsConnected)
            {
                try
                {
                    _Writer.Write(data + "\r\n");
                    _Writer.Flush();
                }
                catch (IOException)
                {
                    IsConnectionError = true;
                    return false;
                }
                catch (ObjectDisposedException)
                {
                    IsConnectionError = true;
                    return false;
                }
                if (OnWriteLine != null)
                {
                    OnWriteLine(this, new WriteLineEventArgs(data));
                }
                return true;
            }
            return false;
        }
        private void _NextAddress()
        {
            _CurrentAddress++;
            if (_CurrentAddress >= _AddressList.Length)
            {
                _CurrentAddress = 0;
            }
        }
        private void _SimpleParser(object sender, ReadLineEventArgs args)
        {
            string rawline = args.Line;
            string[] rawlineex = rawline.Split(new char[] { ' ' });
            string messagecode = "";
            if (rawline[0] == ':')
            {
                messagecode = rawlineex[1];
                ReplyCode replycode = ReplyCode.Null;
                try
                {
                    replycode = (ReplyCode)int.Parse(messagecode);
                }
                catch (FormatException)
                {
                }
                if (replycode != ReplyCode.Null)
                {
                    switch (replycode)
                    {
                        case ReplyCode.Welcome:
                            _IsRegistered = true;
                            break;
                    }
                }
                else
                {
                    switch (rawlineex[1])
                    {
                        case "PONG":
                            DateTime now = DateTime.Now;
                            _LastPongReceived = now;
                            _Lag = now - _LastPingSent;
                            break;
                    }
                }
            }
            else
            {
                messagecode = rawlineex[0];
                switch (messagecode)
                {
                    case "ERROR":
                        IsConnectionError = true;
                        break;
                }
            }
        }
        private void _OnConnectionError(object sender, EventArgs e)
        {
            try
            {
                if (AutoReconnect)
                {
                    // lets try to recover the connection
                    Reconnect();
                }
                else
                {
                    // make sure we clean up
                    Disconnect();
                }
            }
            catch (ConnectionException)
            {
            }
        }
        /// <summary>
        /// 
        /// </summary>
        private class ReadThread
        {
            private IrcConnection _Connection;
            private Thread _Thread;
            private Queue _Queue = Queue.Synchronized(new Queue());
            public Queue Queue
            {
                get
                {
                    return _Queue;
                }
            }
            /// <summary>
            /// 
            /// </summary>
            /// <param name="connection"></param>
            public ReadThread(IrcConnection connection)
            {
                _Connection = connection;
            }
            /// <summary>
            /// 
            /// </summary>
            public void Start()
            {
                _Thread = new Thread(new ThreadStart(_Worker));
                _Thread.Name = "ReadThread (" + _Connection.Address + ":" + _Connection.Port + ")";
                _Thread.IsBackground = true;
                _Thread.Start();
            }
            /// <summary>
            /// 
            /// </summary>
            public void Stop()
            {
                _Thread.Abort();
                try
                {
                    _Connection._Reader.Close();
                }
                catch (ObjectDisposedException)
                {
                }
            }
            private void _Worker()
            {
                try
                {
                    string data = "";
                    try
                    {
                        while (_Connection.IsConnected &&
                               ((data = _Connection._Reader.ReadLine()) != null))
                        {
                            _Queue.Enqueue(data);
                        }
                    }
                    catch (IOException)
                    {
                    }
                    finally
                    {
                        _Connection.IsConnectionError = true;
                    }
                }
                catch (ThreadAbortException)
                {
                    Thread.ResetAbort();
                }
            }
        }
        /// <summary>
        /// 
        /// </summary>
        private class WriteThread
        {
            private IrcConnection _Connection;
            private Thread _Thread;
            private int _HighCount;
            private int _AboveMediumCount;
            private int _MediumCount;
            private int _BelowMediumCount;
            private int _LowCount;
            private int _AboveMediumSentCount;
            private int _MediumSentCount;
            private int _BelowMediumSentCount;
            private int _AboveMediumThresholdCount = 4;
            private int _MediumThresholdCount = 2;
            private int _BelowMediumThresholdCount = 1;
            private int _BurstCount;
            /// <summary>
            /// 
            /// </summary>
            /// <param name="connection"></param>
            public WriteThread(IrcConnection connection)
            {
                _Connection = connection;
            }
            /// <summary>
            /// 
            /// </summary>
            public void Start()
            {
                _Thread = new Thread(new ThreadStart(_Worker));
                _Thread.Name = "WriteThread (" + _Connection.Address + ":" + _Connection.Port + ")";
                _Thread.IsBackground = true;
                _Thread.Start();
            }
            /// <summary>
            /// 
            /// </summary>
            public void Stop()
            {
                _Thread.Abort();
                try
                {
                    _Connection._Writer.Close();
                }
                catch (ObjectDisposedException)
                {
                }
            }
            private void _Worker()
            {
                try
                {
                    try
                    {
                        while (_Connection.IsConnected)
                        {
                            _CheckBuffer();
                            Thread.Sleep(_Connection._SendDelay);
                        }
                    }
                    catch (IOException)
                    {
                    }
                    finally
                    {
                        _Connection.IsConnectionError = true;
                    }
                }
                catch (ThreadAbortException)
                {
                    Thread.ResetAbort();
                }
            }
            #region WARNING: complex scheduler, don't even think about changing it!
            // WARNING: complex scheduler, don't even think about changing it!
            private void _CheckBuffer()
            {
                // only send data if we are succefully registered on the IRC network
                if (!_Connection._IsRegistered)
                {
                    return;
                }
                _HighCount = ((Queue)_Connection._SendBuffer[Priority.High]).Count;
                _AboveMediumCount = ((Queue)_Connection._SendBuffer[Priority.AboveMedium]).Count;
                _MediumCount = ((Queue)_Connection._SendBuffer[Priority.Medium]).Count;
                _BelowMediumCount = ((Queue)_Connection._SendBuffer[Priority.BelowMedium]).Count;
                _LowCount = ((Queue)_Connection._SendBuffer[Priority.Low]).Count;
                if (_CheckHighBuffer() &&
                    _CheckAboveMediumBuffer() &&
                    _CheckMediumBuffer() &&
                    _CheckBelowMediumBuffer() &&
                    _CheckLowBuffer())
                {
                    // everything is sent, resetting all counters
                    _AboveMediumSentCount = 0;
                    _MediumSentCount = 0;
                    _BelowMediumSentCount = 0;
                    _BurstCount = 0;
                }
                if (_BurstCount < 3)
                {
                    _BurstCount++;
                    //_CheckBuffer();
                }
            }
            private bool _CheckHighBuffer()
            {
                if (_HighCount > 0)
                {
                    string data = (string)((Queue)_Connection._SendBuffer[Priority.High]).Dequeue();
                    if (_Connection._WriteLine(data) == false)
                    {
                        ((Queue)_Connection._SendBuffer[Priority.High]).Enqueue(data);
                    }
                    if (_HighCount > 1)
                    {
                        // there is more data to send
                        return false;
                    }
                }
                return true;
            }
            private bool _CheckAboveMediumBuffer()
            {
                if ((_AboveMediumCount > 0) &&
                    (_AboveMediumSentCount < _AboveMediumThresholdCount))
                {
                    string data = (string)((Queue)_Connection._SendBuffer[Priority.AboveMedium]).Dequeue();
                    if (_Connection._WriteLine(data) == false)
                    {
                        ((Queue)_Connection._SendBuffer[Priority.AboveMedium]).Enqueue(data);
                    }
                    _AboveMediumSentCount++;
                    if (_AboveMediumSentCount < _AboveMediumThresholdCount)
                    {
                        return false;
                    }
                }
                return true;
            }
            private bool _CheckMediumBuffer()
            {
                if ((_MediumCount > 0) &&
                    (_MediumSentCount < _MediumThresholdCount))
                {
                    string data = (string)((Queue)_Connection._SendBuffer[Priority.Medium]).Dequeue();
                    if (_Connection._WriteLine(data) == false)
                    {
                        ((Queue)_Connection._SendBuffer[Priority.Medium]).Enqueue(data);
                    }
                    _MediumSentCount++;
                    if (_MediumSentCount < _MediumThresholdCount)
                    {
                        return false;
                    }
                }
                return true;
            }
            private bool _CheckBelowMediumBuffer()
            {
                if ((_BelowMediumCount > 0) &&
                    (_BelowMediumSentCount < _BelowMediumThresholdCount))
                {
                    string data = (string)((Queue)_Connection._SendBuffer[Priority.BelowMedium]).Dequeue();
                    if (_Connection._WriteLine(data) == false)
                    {
                        ((Queue)_Connection._SendBuffer[Priority.BelowMedium]).Enqueue(data);
                    }
                    _BelowMediumSentCount++;
                    if (_BelowMediumSentCount < _BelowMediumThresholdCount)
                    {
                        return false;
                    }
                }
                return true;
            }
            private bool _CheckLowBuffer()
            {
                if (_LowCount > 0)
                {
                    if ((_HighCount > 0) ||
                        (_AboveMediumCount > 0) ||
                        (_MediumCount > 0) ||
                        (_BelowMediumCount > 0))
                    {
                        return true;
                    }
                    string data = (string)((Queue)_Connection._SendBuffer[Priority.Low]).Dequeue();
                    if (_Connection._WriteLine(data) == false)
                    {
                        ((Queue)_Connection._SendBuffer[Priority.Low]).Enqueue(data);
                    }
                    if (_LowCount > 1)
                    {
                        return false;
                    }
                }
                return true;
            }
            // END OF WARNING, below this you can read/change again ;)
            #endregion
        }
        /// <summary>
        /// 
        /// </summary>
        private class IdleWorkerThread
        {
            private IrcConnection _Connection;
            private Thread _Thread;
            /// <summary>
            /// 
            /// </summary>
            /// <param name="connection"></param>
            public IdleWorkerThread(IrcConnection connection)
            {
                _Connection = connection;
            }
            /// <summary>
            /// 
            /// </summary>
            public void Start()
            {
                DateTime now = DateTime.Now;
                _Connection._LastPingSent = now;
                _Connection._LastPongReceived = now;
                _Thread = new Thread(new ThreadStart(_Worker));
                _Thread.Name = "IdleWorkerThread (" + _Connection.Address + ":" + _Connection.Port + ")";
                _Thread.IsBackground = true;
                _Thread.Start();
            }
            /// <summary>
            /// 
            /// </summary>
            public void Stop()
            {
                _Thread.Abort();
            }
            private void _Worker()
            {
                try
                {
                    while (_Connection.IsConnected)
                    {
                        if (_Connection.IsRegistered)
                        {
                            DateTime now = DateTime.Now;
                            int last_ping_sent = (int)(now - _Connection._LastPingSent).TotalSeconds;
                            int last_pong_rcvd = (int)(now - _Connection._LastPongReceived).TotalSeconds;
                            // determins if the response time is ok
                            if (last_ping_sent < _Connection._PingTimeout)
                            {
                                // determines if it need to send another ping yet
                                if (last_pong_rcvd > _Connection._PingInterval)
                                {
                                    _Connection.WriteLine(Rfc2812.Ping(_Connection.Address), Priority.Critical);
                                    _Connection._LastPingSent = now;
                                    _Connection._LastPongReceived = now;
                                } // else connection is fine, just continue
                            }
                            else
                            {
                                _Connection.IsConnectionError = true;
                                break;
                            }
                        }
                        Thread.Sleep(_Connection._IdleWorkerInterval);
                    }
                }
                catch (ThreadAbortException)
                {
                    Thread.ResetAbort();
                }
            }
        }
    }
}
